﻿#include "stdafx.h"
#include "sbcs.h"

//////////////////////////////////////////////////////////////////////////
// 모듈

// 실제 모듈
typedef struct kRealMod
{
	char*				filename;
	int					ref;

	int					flags;
	kModDispFunc		func_disp;

	kpointer			hd;

	struct kRealMod*	next;
} kRealMod;

// 모듈 사용
static struct kImplMod
{
	kcham				isinit;

	char				suffix[64];

	kRealMod*			main;
	kRealMod*			mds;

	kTls*				error;

	kLock				lock;
} k_impl_mod =
{
	FALSE,
#if _SB_WINDOWS_
	"dll",
#else
	"so",
#endif
	NULL, NULL,
	NULL,
	KLOCK_INIT
};

// 미리 정의
static kpointer _k_mod_mm_self(void);
static kpointer _k_mod_mm_open(const char* name, kcham islazy, kcham islocal);
static void _k_mod_mm_close(khandle handle, kcham isunload);
static kcham _k_mod_mm_symbol(kpointer handle, const char* name, kpointer* symbol);

// 초기화
void _k_mod_init(void)
{
	k_return_if_fail(!k_impl_mod.isinit);

	k_impl_mod.isinit = TRUE;
	k_impl_mod.error = k_tls(k_free);
}

// 제거
void _k_mod_disp(void)
{
	kRealMod* node;
	kRealMod* next;

	k_return_if_fail(k_impl_mod.isinit);

	node = k_impl_mod.mds;

	while (node)
	{
		if ((node->flags & KMDB_EXT_NOCLOSURE) == 0 && (node->flags & KMDB_EXT_INFO))
		{
			k_tracef(KLV_WARNING, "k.mod", "opened module [%s]. (ref: %d)",
				node->filename, node->ref);
		}

		next = node->next;

		if (node->func_disp)
		{
			kModDispFunc fdisp = node->func_disp;
			fdisp((kModule*)node);
		}

		_k_mod_mm_close(node->hd, TRUE);
		k_delete(node->filename);
		k_delete(node);

		node = next;
	}

	if (k_impl_mod.main)
	{
		k_delete(k_impl_mod.main->filename);
		k_delete(k_impl_mod.main);
	}
}

// 접미사
kcham k_mod_set_suffix(const char* suffix)
{
	k_return_value_if_fail(suffix, FALSE);

	_k_mod_init();

	K_LOCK(k_impl_mod.lock);
	k_strncpy(k_impl_mod.suffix, suffix, 63);
	K_UNLOCK(k_impl_mod.lock);

	return TRUE;
}

// 오류 설정
void k_mod_set_error(const char* error)
{
	char* tmp;

	_k_mod_init();

	tmp = (char*)k_gettls(k_impl_mod.error);
	k_settls(k_impl_mod.error, k_strdup(error));
	k_delete(tmp);
}

// 오류 얻기
const char* k_mod_get_error(void)
{
	_k_mod_init();

	return (char*)k_gettls(k_impl_mod.error);
}

// 핸들로 찾기
static kRealMod* _k_mod_find_handle(kpointer handle)
{
	kRealMod* md;
	kRealMod* ret;

	K_LOCK(k_impl_mod.lock);

	if (k_impl_mod.main && (kpointer)k_impl_mod.main->hd == handle)
		ret = k_impl_mod.main;
	else
	{
		for (ret = NULL, md = k_impl_mod.mds; md; md = md->next)
		{
			if (handle == (kpointer)md->hd)
			{
				ret = md;
				break;
			}
		}
	}

	K_UNLOCK(k_impl_mod.lock);

	return ret;
}

// 파일 이름으로 찾기
static kRealMod* _k_mod_find_filename(const char* filename)
{
	kRealMod* md;
	kRealMod* ret;

	K_LOCK(k_impl_mod.lock);

	for (ret = NULL, md = k_impl_mod.mds; md; md = md->next)
	{
		if (k_streqv(filename, md->filename))
		{
			ret = md;
			break;
		}
	}

	K_UNLOCK(k_impl_mod.lock);

	return ret;
}

// 열기
kModule* k_mod_open(const char* filename, kpointer data, kint flags)
{
	kRealMod* md = NULL;
	kpointer handle;

	_k_mod_init();
	k_mod_set_error(NULL);

	if (!filename)
	{
		if (!k_impl_mod.main)
		{
			handle = _k_mod_mm_self();

			if (handle)
			{
				md = k_new_0(kRealMod);

#if _SB_WINDOWS_
				K_STMT_BEGIN{
					kwchar buf[260], fname[260];
					ksize_t len;

					GetModuleFileName(NULL, buf, 259);
					k_wdivpath(buf, NULL, 0, fname, 259);

					len = k_conv_utf16_to_utf8(NULL, 0, fname, 0);
					md->filename = k_new(len + 1, char);
					k_conv_utf16_to_utf8(md->filename, len, fname, 0);
				} K_STMT_END;
#endif

				md->hd = handle;
				md->ref = 1;
				md->flags = flags | KMDB_EXT_RESIDENT;

				K_LOCK(k_impl_mod.lock);
				k_impl_mod.main = md;
				K_UNLOCK(k_impl_mod.lock);
			}
		}
		else
		{
			// 증가
			k_atominc(&k_impl_mod.main->ref);
			md = k_impl_mod.main;
		}
	}
	else
	{
		md = _k_mod_find_filename(filename);

		if (md)
		{
			k_atominc(&md->ref);
			md->flags |= flags & (KMDB_EXT_NOCLOSURE | KMDB_EXT_RESIDENT);
		}
		else
		{
			// 로컬 찾기
			char* tname = k_file_exist(filename, NULL) ? k_strdup(filename) : NULL;

			if (!tname)
			{
				// 이름 붙여 찾기
				tname = k_strdupcat(filename, ".", k_impl_mod.suffix, NULL);

				if (!k_file_exist(tname, NULL))
				{
					k_delete(tname);
					tname = NULL;
				}
			}

			// 이름 붙여 찾기
			if (!tname)
			{
				char* dot = k_strrchr(filename, '.');
				char* slash = k_strrchr(filename, K_PATH_SEP);
				tname = (!dot || dot < slash) ? k_strdupcat(filename, ".", k_impl_mod.suffix, NULL) : k_strdup(filename);
			}

			//
			if (tname)
			{
				handle = _k_mod_mm_open(tname,
					(flags & KMDB_LAZY) != 0,
					(flags & KMDB_LOCAL) != 0);
				k_delete(tname);
			}
			else
			{
				char sz[260];
				k_snprintf(sz, 260 - 1, "cannot access file '%s'.", filename);
				k_mod_set_error(sz);
				handle = NULL;
			}

			if (!handle)
				md = NULL;
			else
			{
				kModInitFunc finit;
				const char* reason = NULL;
				kcham resinit = TRUE;
				kcham tmp;

				md = _k_mod_find_handle(handle);

				if (md)
				{
					_k_mod_mm_close(md->hd, TRUE);
					k_atominc(&md->ref);
					md->flags |= flags & (KMDB_EXT_NOCLOSURE | KMDB_EXT_RESIDENT | KMDB_EXT_INFO);
					k_mod_set_error(NULL);
				}
				else
				{
					char* serr = k_strdup(k_mod_get_error());
					k_mod_set_error(NULL);

					md = k_new_0(kRealMod);
					md->filename = k_strdup(filename);
					md->hd = handle;
					md->ref = 1;
					md->flags = flags;
					K_LOCK(k_impl_mod.lock);
					md->next = k_impl_mod.mds;
					k_impl_mod.mds = md;
					K_UNLOCK(k_impl_mod.lock);

					// 기본 찾기
					if (k_mod_sym_ptr((kModule*)md, "module_init", (kpointer*)&finit) ||
						k_mod_sym_ptr((kModule*)md, "module_initialize", (kpointer*)&finit) ||
						k_mod_sym_ptr((kModule*)md, "ModuleInitialize", (kpointer*)&finit))
						resinit = finit((kModule*)md, data, &reason);

					if (resinit)
					{
						tmp = k_mod_sym_ptr((kModule*)md, "module_disp", (kpointer*)&md->func_disp) ||
							k_mod_sym_ptr((kModule*)md, "module_dispose", (kpointer*)&md->func_disp) ||
							k_mod_sym_ptr((kModule*)md, "ModuleDispose", (kpointer*)&md->func_disp);
						k_mod_set_error(serr);

						if (!tmp && (md->flags & KMDB_EXT_INFO))
						{
							k_tracef(KLV_WARNING, "k.mod", "no disposer in module [%s].",
								md->filename);
						}
					}
					else
					{
						char* err = k_strdupcat("module '", filename ? filename : "<unknown>",
							"' initialize failed. (reason: ", reason ? reason : "<unknown>", ")", NULL);

						k_mod_close((kModule*)md);
						md = NULL;

						k_mod_set_error(err);
						k_delete(err);
					}

					k_delete(serr);
				}
			}
		}
	}

	return (kModule*)md;
}

// 닫기
kcham k_mod_close(kModule* self)
{
	kRealMod* md = (kRealMod*)self;

	k_return_value_if_fail(md, FALSE);
	k_return_value_if_fail(md->ref > 0, FALSE);

	if ((md->flags & KMDB_EXT_RESIDENT) != 0)
		return TRUE;

	_k_mod_init();
	k_mod_set_error(NULL);

	k_atomdec(&md->ref);

	if (md->ref <= 0)
	{
		kRealMod* last;
		kRealMod* node;

		if (md->func_disp)
		{
			kModDispFunc fdisp = md->func_disp;
			md->func_disp = NULL;
			fdisp((kModule*)md);
		}

		K_LOCK(k_impl_mod.lock);

		last = NULL;
		node = k_impl_mod.mds;

		while (node)
		{
			if (node == md)
			{
				if (last)
					last->next = node->next;
				else
					k_impl_mod.mds = node->next;

				break;
			}

			last = node;
			node = last->next;
		}

		md->next = NULL;

		K_UNLOCK(k_impl_mod.lock);

		_k_mod_mm_close(md->hd, FALSE);
		k_delete(md->filename);
		k_delete(md);
	}

	return TRUE;
}

// 심볼, 포인터
kcham k_mod_sym_ptr(kModule* self, const char* name, kpointer* ptr)
{
	kRealMod* md = (kRealMod*)self;
	kcham ret;

	k_return_value_if_fail(md, FALSE);
	k_return_value_if_fail(name, FALSE);
	k_return_value_if_fail(ptr, FALSE);

	_k_mod_init();
	k_mod_set_error(NULL);

	ret = _k_mod_mm_symbol(md->hd, name, ptr);

	if (!ret)
	{
		const char* reason = k_mod_get_error();
		char* err = k_strdupcat("{", name, "} ", reason, NULL);

		k_mod_set_error(err);
		k_delete(err);

		*ptr = NULL;
	}

	return ret;
}

// 심볼 함수로
kcham k_mod_sym_func(kModule* self, const char* name, kfunc* func)
{
	kany av;

	k_return_value_if_fail(func, FALSE);

	if (!k_mod_sym_ptr(self, name, &av.p))
	{
		*func = NULL;
		return FALSE;
	}
	else
	{
		*func = av.func;
		return TRUE;
	}
}

// 참조
kint k_mod_ref(kModule* self)
{
	kRealMod* md = (kRealMod*)self;
	return md ? md->ref : 0;
}

// 이름
const char* k_mod_get_name(kModule* self)
{
	kRealMod* md = (kRealMod*)self;
	return md ? md == k_impl_mod.main ? "main" : md->filename : NULL;
}

// 핸들
kpointer k_mod_get_handle(kModule* self)
{
	kRealMod* md = (kRealMod*)self;
	return md ? md->hd : NULL;
}

// 지원 안함
void k_mod_no_support(void)
{
	k_trace(KLV_FATAL, "mod.open", "no module service support.");
}


//////////////////////////////////////////////////////////////////////////
// WIN32용 모듈
#if _SB_WINDOWS_

#if _SB_WINDOWS_DESKTOP_
#include "sbctn.h"
#include <tlhelp32.h>

#define NO_PSAPI		1

// 오류
static void _k_mod_w32_set_error(const char* desc)
{
	const char* tmp;
	char syserr[260];
	kBstr1k bs;

	k_syserr(syserr, 260 - 1, 0);

	if (!desc)
		tmp = syserr;
	else
	{
		k_bstr_format(&bs, 1024, "[%s] %s", desc, syserr);
		tmp = bs.data;
	}

	k_mod_set_error(tmp);
}

// 자기 자신
kpointer _k_mod_mm_self(void)
{
	return K_CAST_INT_TO_PTR(-1);
}

// 열기
kpointer _k_mod_mm_open(const char* name, kcham islazy, kcham islocal)
{
	HMODULE hd;
	kwchar uni[MAX_PATH];

	k_conv_utf8_to_utf16(uni, MAX_PATH - 1, name, 0);
	hd = LoadLibrary(uni);

	if (!hd)
		_k_mod_w32_set_error(name);

	return (kpointer)hd;
}

// 닫기
void _k_mod_mm_close(khandle handle, kcham isunload)
{
	if (handle != K_CAST_INT_TO_PTR(-1))
	if (!FreeLibrary((HMODULE)handle))
		_k_mod_w32_set_error(NULL);
}

// 툴 헬프, 윈도우XP 이상
static kpointer _k_mod_w32_find_toolhelp(const char* name)
{
	HANDLE snapshot;
	MODULEENTRY32W me32;
	kany sym;

	if ((snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0)) == (HANDLE)(kintptr)-1)
		return NULL;

	me32.dwSize = sizeof(me32);
	sym.p = NULL;

	if (Module32First(snapshot, &me32))
	{
		do
		{
			if ((sym.func = (kfunc)GetProcAddress(me32.hModule, name)) != NULL)
				break;
		} while (Module32Next(snapshot, &me32));
	}

	CloseHandle(snapshot);

	return sym.p;
}

#if !NO_PSAPI
// psapi
static kpointer _k_mod_w32_find_psapi(const char* name)
{
	typedef BOOL(WINAPI * stub_EnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD);

	static stub_EnumProcessModules func_EnumProcessModules = NULL;
	static HMODULE dll_psapi = NULL;

	HANDLE proc;
	HMODULE dummy;
	HMODULE* modules;
	DWORD dw;
	kint i, size;
	kany sym;

	if (!func_EnumProcessModules)
	{
		if (!dll_psapi)
		if ((dll_psapi = LoadLibrary(L"psapi")) == NULL)
			return NULL;

		if ((func_EnumProcessModules = (stub_EnumProcessModules)GetProcAddress(dll_psapi, "EnumProcessModules")) == NULL)
			return NULL;
	}

	proc = GetCurrentProcess();

	if (!(*func_EnumProcessModules)(proc, &dummy, sizeof(HMODULE), &dw))
		return NULL;

	size = dw + 10 * sizeof(HMODULE);
	modules = (HMODULE*)k_new(size, kbyte);

	if (!(*func_EnumProcessModules)(proc, modules, size, &dw) || dw > (DWORD)size)
	{
		k_delete(modules);
		return NULL;
	}

	sym.p = NULL;

	for (i = 0; i < (int)(dw / sizeof(HMODULE)); i++)
	{
		if ((sym.func = (kfunc)GetProcAddress(modules[i], name)) != NULL)
			break;
	}

	k_delete(modules);

	return sym.p;
}
#endif

// 심볼 찾기
static kpointer _k_mod_w32_find_any(const char* name)
{
	kpointer ret;

#if NO_PSAPI
	if ((ret = _k_mod_w32_find_toolhelp(name)) == NULL)
		return NULL;
#else
	if ((ret = _k_mod_w32_find_toolhelp(name)) == NULL &&
		(ret = _k_mod_w32_find_psapi(name)) == NULL)
		return NULL;
#endif

	return ret;
}

// 심볼 얻기
kcham _k_mod_mm_symbol(kpointer handle, const char* name, kpointer* symbol)
{
	HMODULE hd = (HMODULE)handle;
	kany sym;

	if (handle != K_CAST_INT_TO_PTR(-1))
		sym.func = (kfunc)GetProcAddress(hd, name);
	else
	{
		sym.func = (kfunc)GetProcAddress(GetModuleHandle(NULL), name);

		if (!sym.func)
			sym.p = _k_mod_w32_find_any(name);
	}

	if (sym.func)
	{
		*symbol = sym.p;
		return TRUE;
	}
	else
	{
		_k_mod_w32_set_error(NULL);
		return FALSE;
	}
}

#else	// _SB_WINDOWS_DESKTOP_

// 자기 자신
kpointer _k_mod_mm_self(void)
{
	return K_CAST_INT_TO_PTR(-1);
}

// 열기
kpointer _k_mod_mm_open(const char* name, kcham islazy, kcham islocal)
{
	k_mod_no_support();
	return NULL;
}

// 닫기
void _k_mod_mm_close(khandle handle, kcham isunload)
{
	k_mod_no_support();
}

// 심볼 얻기
kcham _k_mod_mm_symbol(kpointer handle, const char* name, kpointer* symbol)
{
	k_mod_no_support();
	return FALSE;
}

#endif	// _SB_WINDOWS_DESKTOP_

#endif  // _SB_WINDOWS_


//////////////////////////////////////////////////////////////////////////
// 유닉스 기반 DL SO 모듈
#if _SB_UNIX_

#include <dlfcn.h>

// 오류
static void _k_mod_dl_set_error(void)
{
	const char* err = dlerror();

	if (!err)
		err = "unknown dlerror";

	k_mod_set_error(err);
}

// 자기 자신
kpointer _k_mod_mm_self(void)
{
	kpointer hd = dlopen(NULL, RTLD_GLOBAL | RTLD_LAZY);

	if (!hd)
		_k_mod_dl_set_error();

	return hd;
}

// 열기
kpointer _k_mod_mm_open(const char* name, kcham islazy, kcham islocal)
{
	kpointer hd = dlopen(name, (islocal ? 0 : RTLD_GLOBAL) | (islazy ? RTLD_LAZY : RTLD_NOW));

	if (!hd)
		_k_mod_dl_set_error();

	return hd;
}

// 닫기
void _k_mod_mm_close(khandle handle, kcham isunload)
{
	isunload |= 1;

	if (isunload)
	if (dlclose(handle) != 0)
		_k_mod_dl_set_error();
}

// 심볼 얻기
kcham _k_mod_mm_symbol(kpointer handle, const char* name, kpointer* symbol)
{
	kpointer sym;
	const char* err;

	err = dlerror();
	sym = dlsym(handle, name);

	err = dlerror();

	if (err)
		k_mod_set_error(err);

	*symbol = sym;
	return sym != NULL;
}

#endif  // _SB_UNIX_

